package com.yunji.storage.business;

import com.yunji.base.cache.BaseCache;
import com.yunji.common.base.BaseBusiness;
import com.yunji.common.enums.business.StorageOperateSatus;
import com.yunji.common.enums.business.StorageStatus;
import com.yunji.common.enums.business.StorageType;
import com.yunji.common.enums.common.ErrorEnum;
import com.yunji.common.enums.common.CacheCodeExtend;
import com.yunji.common.model.dto.LoginInfo;
import com.yunji.common.utils.DecimalUtils;
import com.yunji.common.utils.TimeUtils;
import com.yunji.common.utils.bean.BeanUtils;
import com.yunji.common.utils.uuid.UUIDUtils;
import com.yunji.storage.bo.StorageRecordsListQueryBO;
import com.yunji.storage.bo.StorageTicketListQueryBO;
import com.yunji.storage.domain.ExpressDO;
import com.yunji.storage.domain.StorageRecordsDO;
import com.yunji.storage.domain.StorageTicketDO;
import com.yunji.storage.dto.*;
import com.yunji.storage.service.IExpressService;
import com.yunji.storage.service.IStorageRecordsService;
import com.yunji.storage.service.IStorageTicketService;
import com.yunji.storage.vo.StorageTicketVO;
import com.yunji.system.service.TokenService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Project: yunji-coffee
 * @Package: com.yunji.storage.business
 * @Title: StorageTicketBusiness
 * <p>
 * History:
 * Date                     Version     Author          Summary
 * ============================================================
 * 2024-08-16 14:03:50      V1.0        HuaAo       新建类
 */
@Service
public class StorageTicketBusiness extends BaseBusiness {

    @Resource
    IStorageTicketService storageTicketService;

    @Resource
    IStorageRecordsService storageRecordsService;

    @Resource
    StorageInteractBusiness interactBusiness;

    @Resource
    TokenService tokenService;

    @Resource
    IExpressService expressService;

    @Transactional
    public Long create(StorageTicketDTO dto) {
        logs.info("---------------------新增出入库单据------------------------");
        // 校验单据参数
        checkStorageTicket(dto);

        // 校验业务类型和业务单号 是否关联
        if (BeanUtils.isNotNull(dto.getBusinessCode())) {
            interactBusiness.checkTicket(dto.getBusinessType(), dto.getBusinessCode());
            Long businessId = redisCache.get(CacheCodeExtend.PARAM_001002.getInfo());
            dto.setBusinessId(businessId);
        }

        StorageTicketDO storageTicket = new StorageTicketDO();
        BeanUtils.clone(dto, storageTicket);

        String ticketCode = UUIDUtils.intoStorageCode();
        if (Objects.equals(StorageType.OUT_STORAGE.getCode(), dto.getStorageType())) {
            ticketCode = UUIDUtils.outStorageCode();
        }
        storageTicket.setTicketCode(ticketCode);
        storageTicket.setStorageStatus(StorageStatus.WAIT_STORAGE.getCode());
        storageTicket.setProductType(dto.getRecordList().size());
        logs.info("【新增出入库单据】单据信息:{}", storageTicket);

        Assert.isTrue(storageTicketService.create(storageTicket), "新增失败");
        Long ticketId = storageTicket.getTicketId();
        logs.info("【新增出入库单据】单据ID:{}", ticketId);
        logs.info("【新增出入库单据】单据Code:{}", ticketCode);

        List<StorageRecordsDTO> recordList = dto.getRecordList();
        logs.info("【新增出入库单据】出入库商品:{}", recordList);

        List<StorageRecordsDO> newList = new ArrayList<>();
        for (StorageRecordsDTO storageRecordsDTO : recordList) {
            checkStorageRecords(storageRecordsDTO);

            StorageRecordsDO storageRecords = new StorageRecordsDO();
            //复制业务信息
            BeanUtils.clone(storageTicket, storageRecords);

            //复制商品信息
            BeanUtils.clone(storageRecordsDTO, storageRecords);

            newList.add(storageRecords);
        }

        for (StorageRecordsDO storageRecords : newList) {
            Assert.isTrue(storageRecordsService.create(storageRecords), "新增失败");
        }

        return storageTicket.getTicketId();
    }

    /**
     * 修改出入库单据
     *
     * @param dto
     */
    @Transactional
    public void update(StorageTicketDTO dto) {
        logs.info("---------------------出入库操作------------------------");
        LoginInfo user = tokenService.getUser();
        Long userId = user.getUserId();
        String username = user.getNickname();
        logs.info("【出入库操作】登录用户ID:{}", userId);
        logs.info("【出入库操作】登录用户名称:{}", username);

        checkStorageTicket(dto);

        Assert.isTrue(BeanUtils.equals(dto.getStorageStatus(), StorageStatus.WAIT_STORAGE.getCode(), StorageStatus.PART_STORAGE.getCode()), "当前状态不可操作");

        Date time = TimeUtils.getNowDate();
        logs.info("【出入库操作】操作时间:{}", time);

        List<StorageRecordsDTO> recordList = dto.getRecordList();
        logs.info("【出入库操作】出入库商品:{}", recordList);

        //库存操作参数
        StorageOperateDTO operateDTO = new StorageOperateDTO();
        List<StockDTO> dtoList = new ArrayList<>();

        StorageTicketDO storageTicket = new StorageTicketDO();
        BeanUtils.clone(dto, storageTicket);
        storageTicket.setOperateId(userId);
        storageTicket.setOperateName(username);
        storageTicket.setOperateTime(time);

        BeanUtils.clone(dto, operateDTO);

        List<StorageRecordsDO> updateList = new ArrayList<>();

        Integer storageStatus = checkQuantity(dto, updateList, dtoList);
        logs.info("【出入库操作】单据状态:{}", storageStatus);
        storageTicket.setStorageStatus(storageStatus);
        Assert.isTrue(storageTicketService.updateStorageTicket(storageTicket), "修改失败");

        for (StorageRecordsDO storageRecords : updateList) {
            Assert.isTrue(storageRecordsService.update(storageRecords), "修改失败");
        }

        if (BeanUtils.isNotNull(dto.getBusinessId())) {
            List<StorageTicketDO> outVOList = storageTicketService.search(
                    StorageTicketListQueryBO.builder().businessId(dto.getBusinessId())
                            .storageType(StorageType.OUT_STORAGE.getCode()).build());
            for (StorageTicketDO outVO : outVOList) {
                Long ticketId = outVO.getTicketId();
                StorageTicketDO outUpdate = new StorageTicketDO();
                if (BeanUtils.isNotNull(dto.getAlbumPics())) {
                    outUpdate.setTicketId(ticketId);
                    outUpdate.setAlbumPics(dto.getAlbumPics());
                    storageTicketService.updateStorageTicket(outUpdate);
                }
            }
        }

        operateDTO.setList(dtoList);
        interactBusiness.operateStorage(operateDTO);

        // 修改业务单状态
        if (BeanUtils.isNotNull(dto.getBusinessId()) && BeanUtils.isNotNull(dto.getBusinessType())) {
            interactBusiness.updateTicket(dto.getBusinessId(), dto.getBusinessType());
        }
    }

    /**
     * 获取出入库单据详情
     *
     * @param ticketId
     * @return
     */
    public StorageTicketVO select(Long ticketId) {
        logs.info("【出入库详情】出入库单据ID:{}", ticketId);

        StorageTicketDO ticket = storageTicketService.select(ticketId);
        StorageTicketVO vo = new StorageTicketVO();
        BeanUtils.clone(ticket, vo);
        String ticketCode = vo.getTicketCode();
        logs.info("【出入库详情】出入库单据Code:{}", ticketCode);

        if (BeanUtils.isNotNull(ticket.getExpressId())) {
            ExpressDO express = expressService.select(ticket.getExpressId());
            BeanUtils.clone(express, vo);
        }

        StorageRecordsListQueryBO recordsParam = new StorageRecordsListQueryBO();
        recordsParam.setTicketCode(ticketCode);
        List<StorageRecordsDO> recordsList = storageRecordsService.search(recordsParam);
        logs.info("【出入库详情】出入库商品信息:{}", recordsList);
        vo.setBelongId(ticket.getBelongId());
        vo.setBelongName(BaseCache.getBelongName(vo.getBelongId()));
        vo.setRecordList(recordsList);

        return vo;
    }

    /**
     * 填写物流
     *
     * @param dto
     */
    @Transactional
    public void bindExpress(ExpressDTO dto) {
        logs.info("---------------------填写物流------------------------");

        checkExpress(dto);

        ExpressDO express = new ExpressDO();
        BeanUtils.clone(dto, express);
        if (BeanUtils.isNotNull(dto.getExpressId())) {
            Assert.isTrue(expressService.update(express), "修改失败");
        } else {
            Assert.isTrue(expressService.create(express), "新增失败");
        }

        Long ticketId = dto.getTicketId();

        StorageTicketDO outTicket = storageTicketService.select(ticketId);
        Assert.isTrue(BeanUtils.isNotNull(outTicket), "未找到出库单");

        List<StorageTicketDO> list = new ArrayList<>();
        StorageTicketDO outUpdate = new StorageTicketDO();
        outUpdate.setTicketId(ticketId);
        outUpdate.setOperateStatus(StorageOperateSatus.EXPRESS.getCode());
        outUpdate.setExpressId(express.getExpressId());
        list.add(outUpdate);

        Long businessId = outTicket.getBusinessId();
        String businessCode = outTicket.getBusinessCode();
        logs.info("【填写物流】业务ID:{},业务单号:{}", businessId, businessCode);

        if (BeanUtils.isNotNull(businessId)) {
            // 找出入库单据
            List<StorageTicketDO> intoTicketList = storageTicketService.search(StorageTicketListQueryBO.builder()
                    .businessId(businessId).storageType(StorageType.INTO_STORAGE.getCode()).build());
            for (StorageTicketDO intoTicket : intoTicketList) {
                StorageTicketDO intoUpdate = new StorageTicketDO();
                intoUpdate.setTicketId(intoTicket.getTicketId());
                intoUpdate.setExpressId(express.getExpressId());
                list.add(intoUpdate);
            }
        }

        for (StorageTicketDO update : list) {
            Assert.isTrue(storageTicketService.updateStorageTicket(update), "修改失败");
        }

    }

    /**
     * @param dto
     * @Title upload
     * @Desc 上传附件
     * @Date 2024-09-06 14:07:16.318
     */
    public void upload(StorageTicketDTO dto) {
        Long ticketId = dto.getTicketId();
        String albumPics = dto.getAlbumPics();
        logs.info("【上传附件】单据ID:{}", ticketId);
        logs.info("【上传附件】附件照片:{}", albumPics);
        Assert.isTrue(BeanUtils.isNotNull(ticketId), ErrorEnum.CHECKED_811006.getText());
        Assert.isTrue(BeanUtils.isNotNull(albumPics), ErrorEnum.CHECKED_811026.getText());

        StorageTicketDO intoTicket = storageTicketService.select(ticketId);
        Assert.isTrue(BeanUtils.isNotNull(intoTicket), ErrorEnum.BUSINESS_812020.getText());

        List<StorageTicketDO> updateList = new ArrayList<>();

        StorageTicketDO intoUpdate = new StorageTicketDO();
        intoUpdate.setTicketId(ticketId);
        intoUpdate.setOperateStatus(StorageOperateSatus.ALBUMPICS.getCode());
        intoUpdate.setAlbumPics(albumPics);
        updateList.add(intoUpdate);

        Long businessId = intoTicket.getBusinessId();
        String businessCode = intoTicket.getBusinessCode();
        logs.info("【上传附件】业务ID:{},业务单号:{}", businessId, businessCode);

        List<StorageTicketDO> outTicketList = storageTicketService.search(StorageTicketListQueryBO.builder()
                .businessId(businessId).storageType(StorageType.OUT_STORAGE.getCode()).build());
        for (StorageTicketDO outTicket : outTicketList) {
            if (BeanUtils.isNotNull(outTicket)) {
                StorageTicketDO outUpdate = new StorageTicketDO();
                outUpdate.setTicketId(outTicket.getTicketId());
                outUpdate.setAlbumPics(albumPics);
                updateList.add(outUpdate);
            }
        }

        for (StorageTicketDO update : updateList) {
            Assert.isTrue(storageTicketService.updateStorageTicket(update), "修改失败");
        }

    }

    /**
     * 删除出入库单
     *
     * @param
     */
    @Transactional
    public void delete(Long... ticketIds) {
        storageTicketService.delete(ticketIds);
        storageRecordsService.deleteByTicketIds(ticketIds);

    }

    /*-----------------------------------------------------补参/校验--------------------------------------------------*/

    /**
     * 出入库单据参数校验
     *
     * @param dto
     */
    private void checkStorageTicket(StorageTicketDTO dto) {
        Long belongId = dto.getBelongId();
        Integer businessType = dto.getBusinessType();
        List<StorageRecordsDTO> recordList = dto.getRecordList();
        Integer storageType = dto.getStorageType();

        logs.info("【单据参数校验】所属单位:{}", belongId);
        logs.info("【单据参数校验】业务类型:{}", businessType);
        logs.info("【单据参数校验】出入库商品:{}", recordList);
        logs.info("【单据参数校验】出入库类型:{}", storageType);

        Assert.isTrue(BeanUtils.isNotNull(dto.getBelongId()), ErrorEnum.CHECKED_811001.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getBusinessType()), ErrorEnum.CHECKED_811002.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getRecordList()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getStorageType()), ErrorEnum.CHECKED_811005.getText());
    }

    /**
     * 出入库商品参数校验
     *
     * @param dto
     */
    private void checkStorageRecords(StorageRecordsDTO dto) {
        Long productId = dto.getProductId();
        String productSku = dto.getProductSku();
        String productName = dto.getProductName();
        Long unitId = dto.getUnitId();
        BigDecimal expectQuantity = dto.getExpectQuantity();

        logs.info("【商品参数校验】商品ID:{}", productId);
        logs.info("【商品参数校验】商品sku:{}", productSku);
        logs.info("【商品参数校验】商品名称:{}", productName);
        logs.info("【商品参数校验】商品单位:{}", unitId);
        logs.info("【商品参数校验】出入库预计量:{}", expectQuantity);

        Assert.isTrue(BeanUtils.isNotNull(dto.getProductId()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getProductSku()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getProductName()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getUnitId()), ErrorEnum.CHECKED_511025.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getExpectQuantity()), ErrorEnum.CHECKED_811004.getText());
    }

    /**
     * 检查物流参数
     *
     * @param dto
     */
    private void checkExpress(ExpressDTO dto) {
        Long ticketId = dto.getTicketId();
        String ticketCode = dto.getTicketCode();
        String expressCompany = dto.getExpressCompany();
        String expressCode = dto.getExpressCode();
        BigDecimal expressAmount = dto.getExpressAmount();

        logs.info("【物流参数校验】单据ID:{}", ticketId);
        logs.info("【物流参数校验】单据Code:{}", ticketCode);
        logs.info("【物流参数校验】物流公司:{}", expressCompany);
        logs.info("【物流参数校验】物流单号:{}", expressCode);
        logs.info("【物流参数校验】运费:{}", expressAmount);

        Assert.isTrue(BeanUtils.isNotNull(dto.getTicketId()), ErrorEnum.CHECKED_811006.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getTicketCode()), ErrorEnum.CHECKED_811007.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getExpressCompany()), ErrorEnum.CHECKED_811008.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getExpressCode()), ErrorEnum.CHECKED_811009.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getExpressAmount()), ErrorEnum.CHECKED_811010.getText());
    }

    /**
     * 检测预计和实际出入库数量
     *
     * @return
     */
    private Integer checkQuantity(StorageTicketDTO dto, List<StorageRecordsDO> updateList, List<StockDTO> dtoList) {
        List<StorageRecordsDTO> newList = dto.getRecordList();
        Long ticketId = dto.getTicketId();
        StorageRecordsListQueryBO param = new StorageRecordsListQueryBO();
        param.setTicketId(ticketId);
        List<StorageRecordsDO> oldList = storageRecordsService.search(param);
        logs.info("【参数校验】recordId:{},quantity:{}", oldList.get(0).getRecordId(), oldList.get(0).getActualQuantity());
        Map<Long, BigDecimal> map = oldList.stream().collect(Collectors.toMap(StorageRecordsDO::getRecordId, StorageRecordsDO::getActualQuantity, (k1, k2) -> k1));

        BigDecimal actual = BigDecimal.ZERO;
        Integer storageStatus = StorageStatus.ALREADY_STORAGE.getCode();

        for (StorageRecordsDTO storageRecordsDTO : newList) {
            checkStorageRecords(storageRecordsDTO);

            BigDecimal expectQuantity = storageRecordsDTO.getExpectQuantity();
            BigDecimal actualQuantity = storageRecordsDTO.getActualQuantity();
            Assert.isTrue(BeanUtils.isNotNull(actualQuantity), ErrorEnum.BUSINESS_812001.getText());

            StorageRecordsDO storageRecords = new StorageRecordsDO();
            BeanUtils.clone(storageRecordsDTO, storageRecords);
            storageRecords.setOperateTime(TimeUtils.getNowDate());

            StockDTO stockDTO = new StockDTO();
            BeanUtils.clone(storageRecordsDTO, stockDTO);
            dtoList.add(stockDTO);

            BigDecimal oldQuantity = map.get(storageRecordsDTO.getRecordId());
            if (BeanUtils.isNotNull(oldQuantity)) {
                actual = actualQuantity.add(oldQuantity);
                storageRecords.setActualQuantity(actual);
            }
            if (DecimalUtils.less(actual, expectQuantity)) {
                storageStatus = StorageStatus.PART_STORAGE.getCode();
            }
            updateList.add(storageRecords);
        }

        return storageStatus;
    }

}